/*
 * File         : cym_key.c
 * Version      : 0.1.0
 * Author       : Vincent Cheung
 * Date         : Nov. 19, 2013
 * Description  : This file is the key management. And this file transplated 
 *                from key.c by Bruce Tang.
 *
 * Copyright (C) 2013, CYM Co., Ltd.
 *
 * Change Logs  :
 * Date                 Author          Notes
 * Nov. 19, 2013        Vincent         first version
 *
 */

#include "cym_system.h"

/* key lisy */
static list_t key_list = CYM_LIST_OBJECT_INIT(key_list);

/* key listener list */
static list_t key_listener_list = CYM_LIST_OBJECT_INIT(key_listener_list);

/* key module */
static struct key_module key_mod;

/**
 * Register key into system.
 *
 * @retval NULL
 */
void cym_key_register(struct key_module *mod)
{
	/* register key module into system */
	key_mod.init = mod->init;
	key_mod.is_pressed = mod->is_pressed;
}

/**
 * Initialize key module.
 *
 * @retval NULL
 */
void cym_key_init(void)
{
	list_t *node;
	struct key_state *stat;

	/* initialize the key module */
	key_mod.filter = KEY_FILTER_FACTOR;
	key_mod.init();

	/* walk through all the keys to initialze */
	for (node = key_list.next; node != &key_list; node = node->next)
	{
		/* to get key state via key list */
		stat = CYM_LIST_ENTRY(node, struct key_state, list);

		/* initialize all the value */
		stat->pressed_flag  = CYM_FALSE;
		stat->count         = 0;
		stat->duration      = 0;
	}
}

/**
 * Add key to the key list.
 *
 * @param num the numbers of key
 * @param state the key state
 *
 * @note this function shall be invoked by key driver
 *
 * @retval NULL
 */
void cym_key_add(struct key_state *stat, uint8_t num)
{
	uint8_t i;

	/* insert all the key into the key list */
	for (i = 0; i < num; i ++)
	{
		cym_list_insert_after(&key_list, &(stat[i].list));
	}
}

/**
 * Add key listener to the listener list.
 *
 * @param listener the pointer to key listener struct
 *
 * @note this function shall be invoked by application
 *
 * @retval NULL
 */
void cym_key_add_listener(struct key_listener *listener)
{
	/* insert into the system key listener list */
	cym_list_insert_after(&key_listener_list, &(listener->list));
}

/**
 * To get state of the key.
 *
 * @param key the key to get state
 *
 * @retval NULL
 */
static struct key_state *cym_key_get_state(uint8_t key)
{
	list_t *node;
	struct key_state *stat;

	for (node = key_list.next; node != &key_list; node = node->next)
	{
		stat = CYM_LIST_ENTRY(node, struct key_state, list);
		if (stat->key == key)
		{
			return stat;
		}
	}

	return CYM_NULL;
}

/**
 * To check if the key has been pressed.
 *
 * @param key the key to be ckecked
 *
 * @return CYM_TRUE if the key was pressed, otherwise, return CYM_FALSE
 */
static bool_t cym_key_is_pressed(uint8_t key)
{
	struct key_state *stat;

	/* get key state */
	stat = cym_key_get_state(key);

	if (stat != CYM_NULL && stat->pressed_flag)
	{
		return CYM_TRUE;
	}

	return CYM_FALSE;
}

/**
 * To get the duration time since the was pressed.
 *
 * @param key the key to get time
 *
 * @retval NULL
 */
static utime_t cym_key_get_press_duration(uint8_t key)
{
	utime_t current_time = 0;
	utime_t duration_time = 0;
	struct key_state *stat;

	stat = cym_key_get_state(key);

	if (stat != CYM_NULL)
	{
		current_time = cym_tick_get() / CYM_SYSTEM_TICK_PER_SECOND;
		duration_time = current_time - stat->duration;

		return duration_time;
	}

	return 0;
}

/**
 * Notify when key down.
 *
 * @param state the pointer to key state struct
 *
 * @retval NULL
 */
static void cym_key_down_notify(struct key_state *stat)
{
	list_t *node;
	struct key_listener *l;

	for (node = key_listener_list.next; node != &key_listener_list; node = node->next)
	{
		l = CYM_LIST_ENTRY(node, struct key_listener, list);
		/* if the on_key_down is not NULL, invoke it */
		if (l->on_key_down != CYM_NULL)
		{
			l->on_key_down(stat);
		}
	}
}

/**
 * Notify when key up.
 *
 * @param state the pointer to key state struct
 *
 * @retval NULL
 */
static void cym_key_up_notify(struct key_sate *stat)
{
	list_t *node;
	struct key_listener *l;

	for (node = key_listener_list.next; node != &key_listener_list; node = node->next)
	{
		l = CYM_LIST_ENTRY(node, struct key_listener, list);
		/* if the on_key_up is not NULL, invoke it */
		if (l->on_key_up != CYM_NULL)
		{
			l->on_key_up(stat);
		}
	}
}

/**
 * The key task of this system.
 *
 * @note this function shall invoked in main loop
 *
 * @retval NULL
 */
void cym_key_task(void)
{
	list_t *node;
	struct key_state *stat;

	/* walk through all the keys to check their states */
	for (node = key_list.next; node != &key_list; node = node->next)
	{
		/* get the key state from list */
		stat = CYM_LIST_ENTRY(node, struct key_state, list);

		/* to check if the key state has been changed */
		if (key_mod.is_pressed(stat->key) != stat->pressed_flag)
		{
			/* increase the filter count */
			stat->count ++;

			/* the key state has been really changed */
			if (stat->count >= key_mod.filter)
			{
				stat->count = 0;

				/* reset the pressed state */
				stat->pressed_flag = !(stat->pressed_flag);

				/* key has been pressed */
				if (stat->pressed_flag)
				{
					stat->key_value = KEY_NULL;
					cym_key_down_notify(stat);

					stat->duration = cym_tick_get() /
					CYM_SYSTEM_TICK_PER_SECOND;
				}
				/* the key has been released */
				else
				{
					if (cym_key_get_press_duration(
						stat->key) <= SHORT_PRESS_TIME)
					{
						stat->key_value = KEY_SHORT_PRESS;
						cym_key_down_notify(stat);
					}

					cym_key_up_notify(stat);
					stat->key_value = KEY_NULL;
				}
			}
		}
		/* the key state has not been changed */
		else
		{
			/* clear filter count */
			stat->count = 0;

			/* to check the key value */
			if (stat->pressed_flag)
			{
				/* if the duration is greater than KEY_LONG_PRESS*/
				if (cym_key_get_press_duration(stat->key) >= 
					LONG_PRESS_TIME)
				{
					stat->key_value = KEY_LONG_PRESS;
					cym_key_down_notify(stat);
				}

				/* if the duration is greater than KEY_LONG_LONG_PRESS_TIME */
				if (cym_key_get_press_duration(stat->key) >= 
					LONG_LONG_PRESS_TIME)
				{
					stat->key_value = KEY_LONG_LONG_PRESS;
					cym_key_down_notify(stat);
				}
			}
		}
	}
}